#pragma once

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <Windows.h>
#include <cstdint>
#include <functional>
#include <unordered_map>
#include <memory>
#include <list>
#include <array>
#include <any>
#include <bitset>
#include "../detail/list.h"
#include "../detail/timer.h"

namespace coroutine {

/// Coroutine status
enum class Status_ : std::uint32_t {
    NONE = 0,
    READY = 1, ///< Ready to run
    SUSPEND, ///< Suspend for notification
    DEAD, ///< Waiting for deletion
};

enum class WaitStatus_ : std::uint32_t {
    NONE = 0,
    MSG_WAIT = 1,
    SLEEP,
    YIELD,
};

/// pre-defined class Coro
class Coro_;
class Mail;
class Call;

/// Use defined coroutine entry function
using CoroEntry = std::function<void(void*)>;
/// Coroutine handle for windows platform
using CoroHandle = LPVOID;
/// 64 bits coroutine ID to avoid ID wrapping
using CoroID = std::uint64_t;
/// Coroutine hashing map
using CoroMap = std::unordered_map<CoroID, Coro_*>;
/// Coroutine list
using CoroList = _dlist_t<CoroID>;
/// Coroutine message list
using CoroQueue = std::list<Mail>;
/// Bit flag
using BitFlag = std::bitset<8>;
/// Invalid coroutine handle value
constexpr CoroHandle INVALID_CORO_HANDLE = nullptr;
/// Invalid coroutine ID value
constexpr CoroID INVALID_CORO_ID = CoroID(0);
/// Coroutine stack size
constexpr std::size_t STACK_SIZE = 1024 * 256;

/// The data in coroutine's queue
class Mail {
    std::any data_; ///< data
    CoroID sender_ {INVALID_CORO_ID}; ///< sender CoroID

    const Mail& operator=(const Mail&) = delete;
    Mail& operator=(Mail&&) = delete;
    Mail(const Mail&) = delete;
    Mail() {}
    friend class CoroSched_;

public:
    Mail(CoroID sender, const std::any&& any) : data_(std::move(any)), sender_(sender) {}
    Mail(Mail&& rht) : data_(std::move(rht.data_)), sender_(rht.sender_) {}

    operator bool() {
        return data_.has_value();
    }

    operator std::any() {
        return data_;
    }

    const std::type_info& type() const {
        return data_.type();
    }

    CoroID getSender() const {
        return sender_;
    }
};

/// Coroutine scheduler
class CoroSched_ final {
    CoroHandle mainCoro_ {INVALID_CORO_HANDLE}; ///< Main fiber handle, There're must have a fiber for main
                                                /// thread, that is ONLY can switch from fiber to another fiber
    Coro_* current_ {nullptr}; ///< Current coroutine
    CoroMap coros_; ///< all coroutines
    CoroList coroReady_; ///< Coroutines' status which are Status::Ready
    CoroList coroSuspend_; ///< Coroutines' status which are Status::Suspend
    CoroList coroDead_; ///< Coroutines' status which are Status::Dead     
    CoroID coroID_ {1}; ///< The starter of ID pool
    Timer timer_; ///< Timer manager
    Call* remoteCall_ {nullptr}; ///< Remote call

    CoroSched_(const CoroSched_&) = delete;
    CoroSched_(const CoroSched_&&) = delete;
    const CoroSched_& operator=(const CoroSched_&) = delete;

private:
    /// Constructor
    CoroSched_();
    /// The entry function of windows' fiber, please check MSDN 'CreateFiber'
    static void __stdcall entry_(LPVOID lpParam);
    /// Generates a new coroutine ID which maintains by scheduler
    CoroID genCoroID_() { return ++coroID_; }
    /// Status switcher
    /// @param coro coroutine pointer
    /// @param status new status for coro
    void switchTo_(Coro_* coro, Status_ status);

public:
    ~CoroSched_();
    /// Run the scheduler
    /// @retval true means ok
    /// @retval false means quit
    bool sched();
    /// Wake up a suspend coroutine or start a coroutine, switch current coroutine to Status::SUSPEND
    /// @param id The ID of coroutine in Status::Suspend or Status::READAY
    void resume(CoroID id);
    /// Yield from current coroutine to main coroutine, The status of current coroutine from 
    /// Status::Ready to Status::Suspend
    void yield();
    /// Yield from current coroutine to another coroutine, The status of current coroutine from 
    /// Status::Ready to Status::Suspend
    void yield(CoroID id);
    /// Cancel target coroutine and target coroutine will throw an CorotineCancelException
    void cancel(CoroID id);
    /// Wake up a suspend coroutine or start a coroutine, keep current coroutine with Status::READY
    /// @param id The ID of coroutine in Status::Suspend or Status::READAY
    void wakeup(CoroID id);
    /// Terminate coroutine
    /// @param id The ID of coroutine in Status::Suspend or Status::READAY
    void close(CoroID id);
    /// The static method to query instance of scheduler
    static CoroSched_& instance();
    /// Shutdown and cleanup scheduler
    static void shutdown();
    /// Query current coroutine
    CoroID current();
    /// Close all coroutine
    void closeAll();
    /// Push data with type std::any to queue
    /// @param id coroutine ID
    /// @param msg data
    /// @param wakeup wakeup
    void push(CoroID id, std::any&& msg, bool wakeup = false);
    /// Try to pop data
    /// param id coroutine ID
    /// @return data or empty
    Mail pop(CoroID id);
    /// Waiting for data coming and return the data, the suspend coroutine can be waked up by @see resume
    /// @param id coroutine ID
    /// @return data or empty
    Mail pop_wait(CoroID id);
    /// Waiting for data coming and return the data, the suspend coroutine can be waked up by @see resume
    /// @param id coroutine ID
    /// @param milliSeconds Maximum waiting time in millionsecond
    /// @return data or empty
    Mail pop_wait(CoroID id, std::time_t milliSeconds);
    /// Sleep
    /// @param milliSeconds million second
    void sleep(CoroID id, std::time_t milliSeconds);
    /// Returns remote call
    Call& getRemoteCall();

    friend class Coro_;
};

#define CoroSchedRef (coroutine::CoroSched_::instance())

/// Coroutine class
class Coro_ final {
    using Node = _dlist_node_t<CoroID>;
    CoroHandle fiber_ {INVALID_CORO_HANDLE}; ///< The fiber handle for windows platform
    Status_ status_ {Status_::NONE}; ///< Coroutine status
    void* usrPtr_ {nullptr}; ///< user defined data
    CoroEntry fiberEntry_ {nullptr}; ///< user defined entry method
    CoroID coroID_ {INVALID_CORO_ID}; ///< The ID of coroutine
    Node* node_ {nullptr}; ///< The list node
    CoroQueue queue_; ///< Message queue
    BitFlag waitFlag_; ///< Wait flags
    TimerHandle timerHandle_; ///< Timer handle
    bool cancel_flag_{false}; ///< Cancel flag

    Coro_(const Coro_&) = delete;
    Coro_(Coro_&&) = delete;
    const Coro_& operator=(const Coro_&) = delete;

    friend class CoroSched_;

public:
    /// Constructor
    /// @param entry user defined entry method @see FiberEntry
    /// @param usrPtr user defined data
    Coro_(CoroEntry entry, void* usrPtr = nullptr);
    /// Destructor
    ~Coro_();
    /// Query ID of current coroutine
    CoroID getID() const { return coroID_; }
    /// Query message queue
    CoroQueue& queue() { return queue_; }

private:
    /// Query status of current coroutine
    Status_ getStatus() const { return status_; }
    /// Set coroutine's status
    void setStatus(Status_ status) { status_ = status; }
    /// Query user defined data of current coroutine
    void* getUsrPtr() const { return usrPtr_; }
    /// Query user defined entry method of current coroutine
    CoroEntry getEntry() const { return fiberEntry_; }
    /// Query the fiber handle of current coroutine
    CoroHandle getHandle() const { return fiber_; }
    /// Get list node pointer
    Node* node() const { return node_; }
    /// Set list node pointer
    void setNode(Node* node) { node_ = node; }
        /// Check whether waiting for 'sstatus' @see WaitStatus_
    bool isWaitBy(WaitStatus_ sstatus);
    /// Check whether waiting for 'sstatus' @see WaitStatus_ and reset it
    bool checkAndReset(WaitStatus_ sstatus);
    /// Set waiting condition to 'sstatus' @see WaitStatus_
    void waitFor(WaitStatus_ sstatus);
    /// Clean waiting condition for 'sstatus' @see WaitStatus_
    void resetWait(WaitStatus_ sstatus = WaitStatus_::NONE);
    TimerHandle getTimerHandle() { return timerHandle_; }
    void setTimerHandle(TimerHandle timerHandle) { timerHandle_ = timerHandle; }
    void setCancel();
    bool isCancel();
};

#define Coro using namespace coroutine;
#define CoroMain Coro;while(CoroSched_::instance().sched())

}

/// Helper method for @see CoroSched::current
inline static coroutine::CoroID coro_id() {
    return CoroSchedRef.current();
}

/// Spawn a coroutine
/// @return The key of coroutine
template <typename...Args>
inline static coroutine::CoroID coro_spawn(Args...args) {
    return (new coroutine::Coro_(args...))->getID();
}

/// Helper method for @see CoroSched::resume
inline static void coro_resume(coroutine::CoroID id) {
    CoroSchedRef.resume(id);
}

template <typename T>
inline static void coro_resume(coroutine::CoroID id, T&& data) {
    CoroSchedRef.push(id, std::forward<std::any>(data), true);
}

/// Spawn and start a coroutine
/// @return The key of coroutine
template <typename...Args>
inline static coroutine::CoroID coro_start(Args...args) {
    auto id = (new coroutine::Coro_(args...))->getID();
    if (id != coroutine::INVALID_CORO_ID) {
        coro_resume(id);
    }
    return id;
}

/// Helper method for @see CoroSched::yield
inline static void coro_yield() {
    CoroSchedRef.yield();
}

/// Helper method for @see CoroSched::yield
inline static void coro_yield(coroutine::CoroID id) {
    CoroSchedRef.yield(id);
}

/// Helper method for @see CoroSched::sched
inline static void coro_sched() {
    CoroSchedRef.sched();
}

/// Helper method for @see CoroSched::terminate
inline static void coro_close(coroutine::CoroID id) {
    CoroSchedRef.close(id);
}

/// Helper method for @see CoroSched::closeAll
inline static void coro_close_all() {
    CoroSchedRef.closeAll();
}

/// Helper method for @see CoroSched::shutdown
inline static void coro_shutdown() {
    coroutine::CoroSched_::shutdown();
}

/// Helper method for @see CoroSched::push
template <typename T>
inline static void coro_push(coroutine::CoroID id, T&& data) {
    CoroSchedRef.push(id, std::forward<std::any>(data));
}

/// Helper method for @see CoroSched::pop_wait
inline static coroutine::Mail coro_pop_wait() {
    return coroutine::Mail(CoroSchedRef.pop_wait(coro_id()));
}

/// Helper method for @see CoroSched::pop_wait
inline static coroutine::Mail coro_pop_wait(std::time_t milliSeconds) {
    return coroutine::Mail(CoroSchedRef.pop_wait(coro_id(), milliSeconds));
}

/// Helper method for @see CoroSched::pop
inline static coroutine::Mail coro_pop() {
    return coroutine::Mail(CoroSchedRef.pop(coro_id()));
}

/// Helper method for @see CoroSched::sleep
inline static void coro_sleep(time_t milliSecs) {
    return CoroSchedRef.sleep(CoroSchedRef.current(), milliSecs);
}

inline static void coro_cancel(coroutine::CoroID id) {
    CoroSchedRef.cancel(id);
}

inline static void coro_wakeup(coroutine::CoroID id) {
    CoroSchedRef.wakeup(id);
}

/// Chech whether current coroutine is main coroutine
inline static bool coro_is_main() {
    return (coro_id() == 1);
}

/// Quit current coroutine
inline static void coro_quit() {
    if (!coro_is_main()) {
        CoroSchedRef.close(coro_id());
        coro_yield();
    }
}

/// Returns RemoteCall
inline static coroutine::Call& rc() {
    return CoroSchedRef.getRemoteCall();
}
